/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.core.services;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.Serializable;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;
import javax.jcr.Workspace;
import javax.servlet.ServletException;
import offset.nodes.client.dialog.io.model.ExportBook;
import offset.nodes.client.dialog.io.model.ExportData;
import offset.nodes.client.dialog.io.model.ExportFile;
import offset.nodes.client.dialog.io.model.ExportNode;
import offset.nodes.client.dialog.io.model.GetDataSize;
import offset.nodes.client.dialog.io.model.ImportData;
import offset.nodes.client.dialog.io.model.ImportNode;
import offset.nodes.client.model.ServerRequest;
import offset.nodes.client.model.ServerResponse;
import offset.nodes.server.binary.controller.BinaryAttribute;
import offset.nodes.server.error.model.ExceptionId;
import offset.nodes.server.error.model.LoggedException;
import offset.nodes.server.model.RepositoryModel;
import offset.nodes.server.servlet.AbstractRepositoryService;
import offset.nodes.server.servlet.ExportImportManager;
import offset.nodes.server.servlet.Service;
import offset.nodes.server.servlet.ServiceContainer;
import offset.nodes.server.servlet.ServiceMapping;
import offset.nodes.server.servlet.book.Book;
import offset.nodes.server.servlet.book.BookCreator;
import offset.nodes.server.servlet.book.provider.NodeProvider;
import offset.nodes.server.servlet.book.provider.PageProvider;

/**
 *
 * @author Walter L�tgenau
 */
public class IoServices extends AbstractRepositoryService implements ServiceContainer {

    @Override
    public ServiceMapping[] getServiceMappings() {
        return new ServiceMapping[]{
                    new ServiceMapping(new ExportBook.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return exportBook((ExportBook.Request) request);
                }
            }, this),
                    new ServiceMapping(new ExportNode.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return exportNode((ExportNode.Request) request);
                }
            }, this),
                    new ServiceMapping(new ExportFile.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return exportFile((ExportFile.Request) request);
                }
            }, this),
                    new ServiceMapping(new ExportData.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return exportData((ExportData.Request) request);
                }
            }, this),
                    new ServiceMapping(new GetDataSize.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return getDataSize((GetDataSize.Request) request);
                }
            }, this),
                    new ServiceMapping(new ImportNode.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return importNode((ImportNode.Request) request);
                }
            }, this),
                    new ServiceMapping(new ImportData.Request().getId(), new Service() {

                public Serializable service(ServerRequest request) throws Exception {
                    return importData((ImportData.Request) request);
                }
            }, this)
                };

    }

    /**
     * Export the node denoted by path as an epub book.
     *
     * This will not return the node information directly, but a dataId, which can be used in subsequent
     * requests ExportData.Request to retrieve the book data.
     *
     * @param request
     * @return the message including the dataId
     */
    protected ExportBook.Response exportBook(ExportBook.Request request) throws IOException, PathNotFoundException, RepositoryException, ServletException {
        ExportBook.Response response = new ExportBook.Response(ServerResponse.RESULT_SUCCESS);

        ExportImportManager expImp = ExportImportManager.getInstance();
        String id = expImp.openData();

        PageProvider pageProvider = new PageProvider(getServlet(), getRequest(), getResponse());
        NodeProvider nodeProvider = new NodeProvider(getSession(request), request.getPath());
        BookCreator creator = new BookCreator(pageProvider, nodeProvider);

        Book book = creator.createBook(request.getType(), request.getPath(), request.getMaxRecursionLevel(), request.isFollowLinks());
        book.write(expImp.getOutputStream(id));
        expImp.getOutputStream(id).close();

        response.setExportId(id);

        return response;
    }

    /**
     * This class stores a node into the database, after all data fragments have
     * been received.
     */
    class NodeImporter implements ExportImportManager.RepositoryWriter {

        String id;
        ImportNode.Request request;

        public NodeImporter(String id, ImportNode.Request request) {
            this.id = id;
            this.request = request;
        }

        public void write() {
            try {
                ExportImportManager expImp = ExportImportManager.getInstance();

                Workspace workspace = getSession(request).getWorkspace();
                workspace.importXML(request.getPath(), expImp.getInputStream(id), request.getUuidBehaviour());
                expImp.closeData(id);
            } catch (Exception ex) {
                throw new LoggedException(ExceptionId.GENERAL_SYSTEM_EXCEPTION, ex);
            }
        }
    }

    /**
     * Import a node and its subnodes to the path contained in the request.
     *
     * As in exportNode, this will only return a dataId, which can be used in subsequent requests
     * ImportData.Request to pass the data in fragments.
     *
     * @param request
     * @return
     */
    protected ImportNode.Response importNode(ImportNode.Request request) throws IOException {
        ImportNode.Response response = new ImportNode.Response(ServerResponse.RESULT_SUCCESS);

        ExportImportManager expImp = ExportImportManager.getInstance();
        response.setImportId(expImp.openData());
        NodeImporter writer = new NodeImporter(response.getImportId(), request);
        expImp.setWriter(response.getImportId(), writer);

        return response;
    }

    /**
     * Retrieve a fragment of data, limited by maxLength passed in the request,
     * from the ExportImportManager
     *
     * @param request
     * @return the data, as a ExportData.Response message
     */
    protected ExportData.Response exportData(ExportData.Request request) throws FileNotFoundException, IOException {
        ExportData.Response response = new ExportData.Response(ServerResponse.RESULT_SUCCESS);

        ExportImportManager expImp = ExportImportManager.getInstance();
        response.setData(expImp.readData(request.getDataId(), request.getMaxLength()));
        response.setLastFragment(!expImp.moreDataAvailable(request.getDataId()));
        if (response.isLastFragment())
            expImp.closeData(request.getDataId());

        return response;
    }

    /**
     * Return the overall size of the data that can be fetched.
     *
     * @param request
     * @return
     */
    protected GetDataSize.Response getDataSize(GetDataSize.Request request) {
        GetDataSize.Response response = new GetDataSize.Response(ServerResponse.RESULT_SUCCESS);

        ExportImportManager expImp = ExportImportManager.getInstance();
        response.setSize(expImp.getSize(request.getDataId()));

        return response;
    }

    /**
     * Import a fragment of data. The data will be passed to the ExportImportManager.
     * Upon the last fragment the data will be stored in the repository.
     *
     * @param request
     * @return
     */
    protected ImportData.Response importData(ImportData.Request request) throws RepositoryException, IOException {
        ImportData.Response response = new ImportData.Response(ServerResponse.RESULT_SUCCESS);

        ExportImportManager expImp = ExportImportManager.getInstance();
        expImp.writeData(request.getDataId(), request.getData());
        if (request.isLastFragment())
            expImp.getWriter(request.getDataId()).write();


        return response;
    }

    /**
     * Export the node denoted by path.
     *
     * This will not return the node information directly, but a dataId, which can be used in subsequent
     * requests ExportData.Request to retrieve the node data.
     *
     * @param request
     * @return the message including the dataId
     */
    protected ExportNode.Response exportNode(ExportNode.Request request) throws IOException, PathNotFoundException, RepositoryException {
        ExportNode.Response response = new ExportNode.Response(ServerResponse.RESULT_SUCCESS);

        ExportImportManager expImp = ExportImportManager.getInstance();
        String id = expImp.openData();

        getSession(request).exportSystemView(request.getPath(), expImp.getOutputStream(id), request.isIgnoreBinary(), !request.isRecursive());
        response.setExportId(id);

        return response;
    }

    /**
     * Export the file denoted by path.
     *
     * This will not return the node information directly, but a dataId, which can be used in subsequent
     * requests ExportFile.Request to retrieve the node data.
     *
     * @param request
     * @return the message including the dataId
     */
    protected ExportFile.Response exportFile(ExportFile.Request request) throws IOException, PathNotFoundException, RepositoryException {
        final int MAX_BUF_LENGTH = 4096;
        ExportFile.Response response = new ExportFile.Response(ServerResponse.RESULT_SUCCESS);

        ExportImportManager expImp = ExportImportManager.getInstance();
        String id = expImp.openData();

        RepositoryModel model = new RepositoryModel(getSession(request));
        BinaryAttribute data = model.getFile(request.getPath());

        byte[] buf = new byte[MAX_BUF_LENGTH];
        int count = 0;
        while((count = data.getInput().read(buf)) >= 0)
            expImp.getOutputStream(id).write(buf, 0, count);

        response.setExportId(id);
        response.setMimeType(data.getContentType());

        return response;
    }

}
